package cn.swunet.schat.service;

import java.net.DatagramPacket;
import java.util.List;


import cn.swunet.bean.User;
import cn.swunet.dao.UserDao;
import cn.swunet.utils.PacketUtils;

/**
 * 
 * @author: arosen.swunet@qq.com
 *
 * @date: 2020年5月1日
 *
 * @TDDO: 主线程收到数据包 并将之交给该runnable处理
 * 		     判断packet类型之后，按type处理
 */
public class PacketHandlerWorker implements Runnable {
	// 数据缓冲区
	private byte[] data;
	// 数据包总长度
	private int len;
	// 数据包的源发送ip地址
	private String srcIp;
	// 数据包源发送端口
	private int srcPort;
	// udp数据包对象
	private DatagramPacket packet;

	// 数据类型定义
	protected final static int SC_ONLINE = 0x0001;
	protected final static int SC_SENDDATATO = 0x0002;
	protected final static int SC_HEARTBEAT = 0x0003;
	protected final static int SC_GETCONNWITH = 0x0004;
	protected final static int SC_GETDATA = 0x0005;
	protected final static int SC_RECVDATA = 0x0006;
	protected final static int SC_REGISTER = 0x0007;
	protected final static int SC_ACK = 0x0008;
	protected final static int SC_NACK = 0x0009;
	protected final static int SC_ADDFRIEND = 0x000A;
	protected final static int SC_ADDAGREE = 0x000B;
	protected final static int SC_ADDREFUSE = 0x000C;
	
	protected final static int PACKET_HEADER_SIZE = 17;
	protected final static int PACKET_BUFF_SIZE = 1024;
	
	protected final static int SC_SELF_INFO = 0x00A1;
	protected final static int SC_ONLINE_FRIEND_LIST = 0x00A2;
	protected final static int SC_QUERY_USER = 0x00A3;

	@Override
	public void run() {
		// 解析数据包类型
		switch (getDataType()) {
		case SC_ONLINE:
			clientOnlineHandler();
			break;
			
		case SC_SENDDATATO:
			packetForwardHandler();
			break;
			
		case SC_HEARTBEAT:
			heartBeatHandler();
			break;
			
		case SC_GETCONNWITH:
			getCoupleHandler();
			break;
			
		case SC_GETDATA:
			DataComHandler();
			break;
			
		case SC_REGISTER:
			registerHandler();
			break;

		case SC_ADDFRIEND:
			addFriendHandler();
			break;
			
		case SC_ADDAGREE:
			addAgreeHandler();
			break;
			
		case SC_ADDREFUSE:
			addRefuseHandler();
			break;
			
		default:
			break;
		}
	}

	/**
	 * 构造器
	 * 
	 * @param recvSocket
	 * @param data
	 * @param len
	 */
	public PacketHandlerWorker(byte[] data, int len, String srcIp, int srcPort) {
		super();
		this.data = data;
		this.len = len;
		this.srcIp = srcIp;
		this.srcPort = srcPort;
	}

	public PacketHandlerWorker(DatagramPacket packet) {
		this(packet.getData(), packet.getLength(), packet.getAddress().getHostAddress(), packet.getPort());
		this.packet = packet;
	}

	/**
	 * 取缓冲区中签两个字节=============》PacketSize
	 * @author:arosen.swunet@qq.com
	 * @return:int 数据报 size
	 */
	protected int getPacketSize() {
		return ((0xff & data[0]) << 8) | (data[1] & 0x0ff);
	}
	
	/**
	 * 得到缓冲区中数据的size
	 * @author:arosen.swunet@qq.com
	 * @return:int 数据报 size
	 */
	protected int getDataSize() {
		return getPacketSize() - PACKET_HEADER_SIZE;
	}

	/**
	 * 取缓冲区第三个byte=============》DataType
	 * @author:arosen.swunet@qq.com
	 * @return:int 数据报 类型
	 */
	protected int getDataType() {
		return 0x0ff & data[2];
	}
	
	/**
	 * 取出缓冲区中的数据
	 * @author:arosen.swunet@qq.com
	 * @return:byte[] 数据
	 */
	protected byte[] getData() {
		byte[] packet_data = new byte[getDataSize()];
		System.arraycopy(data, 17, packet_data, 0, getDataSize());
		return packet_data;
	}

	/**
	 * 返回data中携带的参数列表 \r\n为分隔符
	 * @author:arosen.swunet@qq.com
	 * @param srcStr
	 * @return:String[]
	 */
	protected String[] getRequetData(byte[] data) {
		String str = new String(data);
		return str.split("\r\n");
	}
	
	/**
	 * 返回参数列表中的data_type的值
	 * @author:arosen.swunet@qq.com
	 * @param srcStr
	 * @return:int 成功：非0   失败：返回0
	 */
	protected int getRequetDatatype(String[] params) {
		int type = 0;
		for (String str : params) {
			if (str.contains("data_type")) {
				String str_type = str.split(":")[1];
				if (str_type.equals("self_info")) {
					type = SC_SELF_INFO;
				}else if (str_type.equals("online_friend_list")) {
					type = SC_ONLINE_FRIEND_LIST;
				}else if (str_type.equals("query_user")) {
					type = SC_QUERY_USER;
				}else {
					break;
				}
			}
		}
		
		return type;
	}
	
	/**
	 * 取缓冲区第4~7个byte=============》DesUserId
	 * @author:arosen.swunet@qq.com
	 * @return:int 数据报 接收方 user id
	 */
	protected int getDesUserId() {
		return ((data[3] & 0xff) << 24) | ((data[4] & 0xff) << 16) | ((data[5] & 0xff) << 8) | (data[6] & 0xff);
	}

	/**
	 * 获取数据报发送目标 点分十进制 IP
	 * @author:arosen.swunet@qq.com
	 * @return:String 数据报目标 点分十进制 IP
	 */
	protected String getDesIp() {
		return (0xff & data[7]) + "." + (0xff & data[8]) + "." + (0xff & data[9]) + "." + (0xff & data[10]);
	}

	/**
	 * 获取数据报发送目标 port
	 * @author:arosen.swunet@qq.com
	 * @return:int 数据报目标 port
	 */
	protected int getDesPort() {
		return ((0xff & data[11]) << 8) | (0xff & data[12]);
	}

	/**
	 * 获取数据报 源发送方 user id
	 * @author:arosen.swunet@qq.com
	 * @return:int 数据报 源发送方 user id
	 */
	protected int getSrcUserId() {
		return ((data[13] & 0xff) << 24) | ((data[14] & 0xff) << 16) | ((data[15] & 0xff) << 8) | (data[16] & 0xff);
	}

	/**
	 * 将 str ip 转换成 byte[]
	 * @author:arosen.swunet@qq.com
	 * @return:byte[]
	 */
	protected byte[] getStrIpToBytes(String ip) {
		String [] ip_arry = ip.split("\\.");
		
		if (ip_arry.length <= 0)
			return null;
		
		byte [] b_ip = new byte[4];
		for(int i = 0; i < ip_arry.length; i++)
			b_ip[i] = (byte)Integer.parseInt(ip_arry[i]);
		
		return b_ip;
	}
	
	/**
	 * int userid ----> byte[]
	 * @author:arosen.swunet@qq.com
	 * @return:byte[]
	 */
	protected byte[] getIntIdToBytes(int id) {
		byte[] b_id = new byte[4];
		b_id[0] = (byte)((id >> 24) & 0xff);
		b_id[1] = (byte)((id >> 16) & 0xff);
		b_id[2] = (byte)((id >> 8) & 0xff);
		b_id[3] = (byte)(id & 0xff);
		
		return b_id;
	}
	
	/**
	 * 将 int port 转换为 byte[]
	 * @author:arosen.swunet@qq.com
	 * @return:byte[]
	 */
	protected byte[] getIntPortToBytes(int port) {
		if (port > 65535) 
			return null;
		
		byte[] b_port = new byte[2];
		b_port[0] = (byte)(port / 256);
		b_port[1] = (byte)(port % 256);
		
		return b_port;
	}
	
	/**
	 * 客户端上线，记录客户端的ip port到数据库
	 * @author:arosen.swunet@qq.com
	 * @return:boolean
	 */
	protected void clientOnlineHandler() {
		//进行用户验证
		String str_data = PacketUtils.getDataToString(data, getPacketSize() - 17);
		
		//没有附带数据
		if (str_data == null)
			return;
		
		//验证用户并添加记录
		String[] verifyMessage = str_data.split("\r\n");
		boolean isSuccess = PacketUtils.verifyUser(verifyMessage[0].split(":")[1],
				verifyMessage[1].split(":")[1],getSrcUserId(), srcIp, srcPort);
		
		//构建响应包
		byte[] res_data = PacketUtils.getHeader(getIntIdToBytes(getSrcUserId()), getStrIpToBytes(srcIp), 
				getIntPortToBytes(srcPort), isSuccess?SC_ACK:SC_NACK);

		//将该数据报发送出去
		if (!new PacketSender(res_data, srcIp, srcPort).doSend()) {
			System.out.println("clientOnline respond: Failed To Send Response Package To [" + srcIp+":"+srcPort+"] ！");
		}
	}
	
	/**
	 * udp打洞失败 客户端请求服务器进行消息转发
	 * @author:arosen.swunet@qq.com
	 * @return:void
	 */
	public void packetForwardHandler() {
		
		//取出数据报目标user id
		int desUID = getDesUserId();
		
		//new 出一个 userDao 
		User user = new UserDao().getUserById(desUID);
		
		//查询 user 在线信息
		String userActiveIp = user.getActiveIp();
		int userActivePort = user.getActivePort();

		//查询 active IP & active port
		if(userActiveIp != null && userActivePort != 0) {
			
			//构建data
			data[2] = SC_RECVDATA;
			
			//使用socket 发送 data
			PacketSender sender = new PacketSender(data, userActiveIp, userActivePort);
			respond(sender.doSend());
		}
	}

	/**
	 * 根据数据发送返回 确定 向 packet 源 host 发送ACK / NACK 依赖源 packet data
	 * @author:arosen.swunet@qq.com
	 * @param boolean isAck 
	 * @return:void
	 */
	public void respond(boolean isAck) {
		byte[] resp = null;
		if(!isAck) {
			//构建响应数据报
			resp = PacketUtils.constructPacket(
					PacketUtils.getNackHeader(getIntIdToBytes(getDesUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort)),
					"client is outline");
		}else {
			//构建响应数据报
			resp = PacketUtils.constructPacket(
					PacketUtils.getAckHeader(getIntIdToBytes(getDesUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort)),"");
		}
		
		//响应 不用重传
		if (!new PacketSender(resp, srcIp, srcPort).doSend(false)) {
			System.out.println("packetForward respond: Failed To Send Response Package To [" + srcIp+":"+srcPort+"] ！");
		}
	}
	
	/**
	 * 心跳数据报处理 不需要重传及响应
	 * @author:arosen.swunet@qq.com
	 * @return:void
	 */
	public void heartBeatHandler() {
		//从data中取出源uid
		int src_uid = getSrcUserId();
		//将源uid放到UserManager
		UserManagerWorker.addFreshUser(src_uid);
	}
	
	/**
	 * 客户端请求 udp 打洞
	 * @author:arosen.swunet@qq.com
	 * @return:void
	 */
	public void getCoupleHandler() {
		//1.先查询目标好友是否在线
		//		不在线:回复Nack，结束
		//		在    线:回复ack
		User targetUser = UserManagerWorker.getOnlineUser(getDesUserId());
		User srcUser = UserManagerWorker.getOnlineUser(getSrcUserId());
		
		boolean flag = false;
		
		if (targetUser == null || srcUser == null) {
			respond(flag);
			return;
		}
		
		/****************通知目标客户机***************/
		//构建packet
		byte[] packet_data = new byte[PACKET_HEADER_SIZE];
		byte[] b_srcIP = getStrIpToBytes(srcIp);
		byte[] b_src_port = getIntPortToBytes(srcPort);
		byte[] b_src_id = getIntIdToBytes(getSrcUserId());
		
		//拷贝一个副本
		System.arraycopy(data, 0, packet_data, 0, packet_data.length);
		//size
		packet_data[0] = (byte)0;
		packet_data[1] = (byte)17;
		
		//des id
		System.arraycopy(b_src_id, 0, packet_data, 3, b_src_id.length);
		//des ip
		System.arraycopy(b_srcIP, 0, packet_data, 7, b_srcIP.length);
		//des port
		System.arraycopy(b_src_port, 0, packet_data, 11, b_src_port.length);
		//src id
		packet_data[13] = packet_data[14] = packet_data[15] = packet_data[16] = 0;
		//向目标机发送SC_GETCONNWITH,目标ip为srcip port 为src port
		PacketSender sender = new PacketSender(packet_data, targetUser.getActiveIp(), targetUser.getActivePort());
		//f发送给目的机器并响应给源客户机
		
		boolean isSuccess = sender.doSend();
		respond(isSuccess);

		/****************通知源客户机***************/
		if(!isSuccess)
			return;
		
		//构建packet data
		data[0] = (byte)0;
		data[1] = (byte)17;
		
		b_src_id = getIntIdToBytes(targetUser.getId());
		b_srcIP = getStrIpToBytes(targetUser.getActiveIp());
		b_src_port = getIntPortToBytes(targetUser.getActivePort());
		
		//des id
		System.arraycopy(b_src_id, 0, packet_data, 3, b_src_id.length);
		//des ip
		System.arraycopy(b_srcIP, 0, packet_data, 7, b_srcIP.length);
		//des port
		System.arraycopy(b_src_port, 0, packet_data, 11, b_src_port.length);
		//src id
		data[13] = data[14] = data[15] = data[16] = 0;
		//发送出去
		new PacketSender(data, srcIp, srcPort).doSend();
	}
	
	/**
	 * 客户端向服务器请求数据
	 * @author:arosen.swunet@qq.com
	 * @return:void
	 */
	public void DataComHandler() {
		//取出数据，并转换成String[]
		String[] str_data = getRequetData(getData());
		//从data中获取data_type
		int data_type = getRequetDatatype(str_data);
		
		boolean isLegal = true;
		
		byte[] header = null;
		byte[] packet = null;
		
		switch (data_type) {
			case SC_SELF_INFO:
			case SC_QUERY_USER:
				//从packet中取出username
				String self_name = str_data[1].split(":")[1];
				if (self_name == null) {
					isLegal = false;
					break;
				}
				//根据uid取出user,转换成String[]
				String self_info = null;
				
				//SC_SELF_INFO 通过name
				if(data_type == SC_SELF_INFO) {
					
					self_info = new UserDao().getUserByName(self_name).getInfo();
				}

				//SC_QUERY_USER 通过id
				else {
					self_info = new UserDao().getUserById(Integer.parseInt(self_name)).getInfo();
				}
				
				header = PacketUtils.getHeader(getIntIdToBytes(getSrcUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort), SC_RECVDATA);
				packet = PacketUtils.constructPacket(header, self_info);
				break;
			case SC_ONLINE_FRIEND_LIST:
				String onlineFriends = null;
				List<User> users = new UserDao().getFriends(getSrcUserId());
				for(User user:users) onlineFriends += user.getInfo();
				header = PacketUtils.getHeader(getIntIdToBytes(getSrcUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort), SC_RECVDATA);
				packet = PacketUtils.constructPacket(header, onlineFriends);
				break;

			default:
				break;
		}
		
		if (!isLegal) {
			header = PacketUtils.getNackHeader(getIntIdToBytes(getDesUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort));
			packet = PacketUtils.constructPacket(header, "request data_type illegal!");
		}
		new PacketSender(packet, srcIp, srcPort).doSend();
	}
	
	/**
	 * 用户注册 用户名+密码
	 * @author:arosen.swunet@qq.com:void
	 */
	public void registerHandler() {
		UserDao userDao = new UserDao();
		String[] user_info = getRequetData(getData());
		byte[] header = null;
		byte[] packet = null;
		
		String user_name = user_info[0].split(":")[1];
		
		if(userDao.getUserByName(user_name) != null) {
			header = PacketUtils.getNackHeader(getIntIdToBytes(getDesUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort));
			packet = PacketUtils.constructPacket(header, "User name is already in use");
			new PacketSender(packet, srcIp, srcPort).doSend(false);
		}
		
		String user_pass = user_info[1].split(":")[1];
		
		User user = new User(user_name, user_pass);
		if (userDao.addUser(user)>0) {
			header = PacketUtils.getAckHeader(getIntIdToBytes(getDesUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort));
			packet = PacketUtils.constructPacket(header, "register success");
			new PacketSender(packet, srcIp, srcPort).doSend(false);
		}
		else {
			header = PacketUtils.getNackHeader(getIntIdToBytes(getDesUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort));
			packet = PacketUtils.constructPacket(header, "Server Internal Error");
			new PacketSender(packet, srcIp, srcPort).doSend(false);
		}
	}
	
	/**
	 * 客户端发起添加好友请求
	 * @author:arosen.swunet@qq.com
	 */
	public void addFriendHandler() {
		byte[] header = null;
		byte[] packet = null;
		
		String[] param = getRequetData(getData());
		String user_id = param[0].split(":")[1];
		//取出目标好友id
		UserDao userDao = new UserDao();
		User desUser = UserManagerWorker.getOnlineUser(Integer.parseInt(user_id));
		User srcUser = UserManagerWorker.getOnlineUser(getSrcUserId());
		//查询目标好友是否在线（userManager）
		if (desUser == null) {
			header = PacketUtils.getNackHeader(getIntIdToBytes(getDesUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort));
			packet = PacketUtils.constructPacket(header, "user user does not exist or user is offline");
			new PacketSender(packet, srcIp, srcPort).doSend(false);
			return;
		}
		//在线返回ACK
		else {
			header = PacketUtils.getAckHeader(getIntIdToBytes(getDesUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort));
			packet = PacketUtils.constructPacket(header, "");
			new PacketSender(packet, srcIp, srcPort).doSend(false);
		}
		//向目标好友转发添加好友信息，将源好友 信息发送到客户端，等待客户端反馈
		header = PacketUtils.getHeader(getIntIdToBytes(desUser.getId()), getStrIpToBytes(desUser.getActiveIp()),
				getIntPortToBytes(desUser.getActivePort()), SC_ADDFRIEND);
		packet = PacketUtils.constructPacket(header, srcUser.getId()+"\r\n"+srcUser.getUsername()+"\r\n");
		new PacketSender(packet, srcIp, srcPort).doSend();
	}
	

	/**
	 * 处理同意客户端对被添加好友的反馈
	 * @author:arosen.swunet@qq.com
	 * @retrun:void
	 */
	public void addAgreeHandler() {
		byte[] header = null;
		byte[] packet = null;
		
		UserDao userDao = new UserDao();
		
		//获取源id 目的id
		int srcid = getSrcUserId();
		int desid = getDesUserId();
		//在friend表中添加双向好友记录
		if (userDao.addFriend(srcid, desid) > 0) {
			//反馈添加结果给源客户端(ACK)
			header = PacketUtils.getAckHeader(getIntIdToBytes(getSrcUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort));
			packet = PacketUtils.constructPacket(header, "");
			new PacketSender(packet, srcIp, srcPort).doSend(false);
			//通知目标好友添加结果(启用重传 SC_ADDAGREE)
			User user = userDao.getUserById(desid);
			header = PacketUtils.getHeader(getIntIdToBytes(srcid), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort), SC_ADDAGREE);
			new PacketSender(packet, user.getActiveIp(), user.getActivePort()).doSend();
		}
		//添加记录失败
		else {
			header = PacketUtils.getNackHeader(getIntIdToBytes(srcid), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort));
			packet = PacketUtils.constructPacket(header, "Server Add Friend Record Failed!");
			new PacketSender(packet, srcIp, srcPort).doSend(false);
		}
	}
	
	/**
	 * 客户端拒绝对方添加
	 * @author:arosen.swunet@qq.com
	 * @retrun:void
	 */
	public void addRefuseHandler() {
		byte[] header = null;
		byte[] packet = null;
		
		//获取源id 目的id
		int srcid = getSrcUserId();
		int desid = getDesUserId();
		
		//反馈被添加客户端(ACK)
		header = PacketUtils.getAckHeader(getIntIdToBytes(getSrcUserId()), getStrIpToBytes(srcIp), getIntPortToBytes(srcPort));
		packet = PacketUtils.constructPacket(header, "");
		new PacketSender(packet, srcIp, srcPort).doSend(false);
		
		//通知申请添加客户端(启用重传 SC_ADDREFUSE)
		User user = new UserDao().getUserById(desid);
		header = PacketUtils.getHeader(getIntIdToBytes(user.getId()), getStrIpToBytes(user.getActiveIp()), 
				getIntPortToBytes(user.getActivePort()), SC_ADDREFUSE);
		packet = PacketUtils.constructPacket(header, "The user rejected your request to add a friend!");
		new PacketSender(packet, user.getActiveIp(), user.getActivePort()).doSend();
	}
}
